1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.graphics.g2d.textrenderer; 12 import hip.graphics.mesh; 13 import hip.math.matrix; 14 import hip.api.data.font; 15 import hip.hiprenderer; 16 import hip.assetmanager; 17 public import hip.graphics.orthocamera; 18 public import hip.api.graphics.batch; 19 public import hip.api.graphics.text : HipTextAlign; 20 21 /** 22 * Don't change those names. If the variable names are changed, the shaders should stop working 23 */ 24 @HipShaderInputLayout struct HipTextRendererVertex 25 { 26 import hip.math.vector; 27 Vector3 vPosition; 28 Vector2 vTexST; 29 30 this(Vector3 vPosition, Vector2 vTexST) 31 { 32 this.vPosition = vPosition; 33 this.vTexST = vTexST; 34 } 35 36 static enum size_t floatsCount = (HipTextRendererVertex.sizeof / float.sizeof); 37 static enum size_t quadsCount = floatsCount*4; 38 } 39 40 @HipShaderVertexUniform("Cbuf") 41 struct HipTextRendererVertexUniforms 42 { 43 Matrix4 uModel = Matrix4.identity; 44 Matrix4 uView = Matrix4.identity; 45 Matrix4 uProj = Matrix4.identity; 46 } 47 48 @HipShaderFragmentUniform("FragVars") 49 struct HipTextRendererFragmentUniforms 50 { 51 float[4] uColor = [1,1,1,1]; 52 } 53 54 55 enum TextRendererPoolSize = 40_000; 56 private __gshared Shader bmTextShader = null; 57 58 /** 59 * This class oculd be refactored in the future to actually 60 * use a spritebatch for its drawing. 61 */ 62 class HipTextRenderer : IHipDeferrableText, IHipBatch 63 { 64 mixin(HipDeferredLoad); 65 IHipFont font; 66 Mesh mesh; 67 index_t[] indices; 68 HipTextRendererVertex[] vertices; 69 70 protected HipColor color; 71 protected HipOrthoCamera camera; 72 protected float managedDepth = 0; 73 private uint quadsCount; 74 private uint lastDrawQuadsCount; 75 bool shouldRenderLineBreak, shouldRenderSpace; 76 private __gshared uint[] linesWidths; 77 78 this(HipOrthoCamera camera, index_t maxIndices = index_t_maxQuadIndices) 79 { 80 if(bmTextShader is null) 81 { 82 bmTextShader = HipRenderer.newShader(HipShaderPresets.BITMAP_TEXT); 83 bmTextShader.addVarLayout(ShaderVariablesLayout.from!HipTextRendererVertexUniforms); 84 bmTextShader.addVarLayout(ShaderVariablesLayout.from!HipTextRendererFragmentUniforms); 85 bmTextShader.setBlending(HipBlendFunction.SRC_ALPHA, HipBlendFunction.ONE_MINUS_SRC_ALPHA, HipBlendEquation.ADD); 86 const Viewport v = HipRenderer.getCurrentViewport(); 87 bmTextShader.uProj = Matrix4.orthoLH(0, v.width, v.height, 0, 0.01, 100); 88 bmTextShader.setDefaultBlock("FragVars"); 89 bmTextShader.bind(); 90 bmTextShader.sendVars(); 91 } 92 mesh = new Mesh(HipVertexArrayObject.getVAO!HipTextRendererVertex, bmTextShader); 93 //6 indices per quad 94 indices = new index_t[](maxIndices); 95 vertices = new HipTextRendererVertex[](TextRendererPoolSize); 96 mesh.createIndexBuffer(maxIndices, HipBufferUsage.STATIC); 97 mesh.createVertexBuffer(cast(index_t)vertices.length, HipBufferUsage.DYNAMIC); 98 mesh.sendAttributes(); 99 HipVertexArrayObject.putQuadBatchIndices(indices, maxIndices / 6); 100 mesh.setVertices(vertices); 101 mesh.setIndices(indices); 102 if(camera is null) 103 camera = new HipOrthoCamera(); 104 this.camera = camera; 105 106 import hip.global.gamedef; 107 //Promise it won't modify 108 setFont(cast(IHipFont)HipDefaultAssets.font); 109 } 110 111 void setCurrentDepth(float depth){managedDepth = depth;} 112 113 void setFont(IHipFont font) 114 { 115 if(this.font !is null && font !is this.font) 116 { 117 draw(); 118 } 119 this.font = font; 120 } 121 122 void setColor(HipColor color) 123 { 124 if(this.color != color) 125 { 126 if(this.color != HipColor.no) 127 draw(); 128 bmTextShader.uColor = HipColorf(color); 129 } 130 this.color = color; 131 } 132 133 /** 134 * Implementation for unchanging text. 135 * The text will be saved, represented as an internal ID to a managed static HipText. Which means the texture will be baked 136 * so it is possible to actually draw it a lot faster as all the preprocessings are done once. 137 */ 138 void draw(string text, int x, int y, HipTextAlign alignh = HipTextAlign.CENTER, HipTextAlign alignv = HipTextAlign.CENTER, int boundsWidth = -1, int boundsHeight = -1, bool wordWrap = false) 139 { 140 import hip.util.string : toUTF32; 141 import hip.api.graphics.text; 142 143 dstring str = text.toUTF32; 144 int vI = quadsCount*4; //vertex buffer index 145 bool isFirstLine = true; 146 int yoffset = 0; 147 foreach(HipLineInfo lineInfo; font.wordWrapRange(str, wordWrap ? boundsWidth : -1)) 148 { 149 if(!isFirstLine) 150 { 151 yoffset+= font.lineBreakHeight; 152 } 153 isFirstLine = false; 154 int xoffset = 0; 155 int displayX = void, displayY = void; 156 getPositionFromAlignment(x, y, lineInfo.width, 0, alignh, alignv, displayX, displayY, boundsWidth, boundsHeight); 157 for(int i = 0; i < lineInfo.line.length; i++) 158 { 159 int kerning = lineInfo.kerningCache[i]; 160 const(HipFontChar)* ch = lineInfo.fontCharCache[i]; 161 162 switch(lineInfo.line[i]) 163 { 164 case ' ': 165 if(!shouldRenderSpace) 166 { 167 xoffset+= font.spaceWidth; 168 break; 169 } 170 goto default; 171 default: 172 if(ch is null) continue; 173 ch.putCharacterQuad( 174 cast(float)(xoffset+displayX+ch.xoffset+kerning), 175 cast(float)(yoffset+displayY+ch.yoffset), managedDepth, 176 cast(HipTextRendererVertexAPI[])vertices[vI..vI+4] 177 ); 178 vI+= 4; 179 xoffset+= ch.xadvance; 180 } 181 } 182 } 183 quadsCount = vI/4; 184 } 185 186 ///This way it will reallocate once. 187 void addVertices(void[] vertices, IHipFont font) 188 { 189 if(vertices.length > 0) 190 { 191 setFont(font); 192 HipTextRendererVertex[] theVerts = cast(HipTextRendererVertex[])vertices; 193 if(theVerts.length+quadsCount*4 > this.vertices.length) 194 this.vertices.length = theVerts.length+quadsCount*4; 195 this.vertices[quadsCount*4..quadsCount*4+theVerts.length] = theVerts[0..$]; 196 quadsCount+= theVerts.length/4; 197 } 198 } 199 200 void draw() 201 { 202 if(font is null) 203 { 204 import hip.error.handler; 205 ErrorHandler.showWarningMessage("Font Missing", "No font attached on HipTextRenderer"); 206 return; 207 } 208 if(quadsCount - lastDrawQuadsCount != 0) 209 { 210 mesh.bind(); 211 this.font.texture.bind(); 212 mesh.shader.setVertexVar("Cbuf.uProj", camera.proj, true); 213 mesh.shader.setVertexVar("Cbuf.uView", camera.view, true); 214 mesh.shader.sendVars(); 215 216 size_t start = lastDrawQuadsCount*4; 217 size_t end = quadsCount*4; 218 219 mesh.updateVertices(cast(float[])vertices[start..end], cast(int)start); 220 mesh.draw((quadsCount - lastDrawQuadsCount)*6, HipRendererMode.TRIANGLES, lastDrawQuadsCount*6); 221 font.texture.unbind(); 222 mesh.unbind(); 223 } 224 lastDrawQuadsCount = quadsCount; 225 } 226 /** 227 * Flush should be took care since it could make it rewrite to the same part of the buffer agin. 228 * While shadow buffering is not implemented, use it as that. 229 */ 230 void flush() 231 { 232 draw(); 233 lastDrawQuadsCount = quadsCount = 0; 234 } 235 }